﻿using OpenCvSharp;

namespace UDI.Core
{
    internal class CVImg
    {

        bool CanShow = false;
        public CVImg(bool canShow)
        {
            CanShow = canShow;
        }


        private void ShowCV(string title, Mat img)
        {
            if (CanShow)
                Cv2.ImShow(title, img);
        }

        /// <summary>
        /// 缩放转灰度
        /// </summary>
        /// <param name="image">原图</param>
        /// <returns></returns>
        private (Mat, double) ScaleImgToGray(Mat image)
        {
            int width = 1000;
            int hight = Convert.ToInt32((double)image.Height / ((double)image.Width / width));

            Mat gray = new();
            double scale = 1.0;
            //1原图像大小调整，提高运算效率
            if (image.Width > width)
            {
                scale = (double)width / image.Width;//缩放比实际
                Cv2.Resize(image, gray, new OpenCvSharp.Size(width, hight));
            }
            else
            {
                gray = new Mat(image, new OpenCvSharp.Rect(0, 0, image.Width, image.Height));
            }


            //2转灰度
            Cv2.CvtColor(gray, gray, ColorConversionCodes.RGB2GRAY);
            ShowCV("1-缩放灰度转换", gray);
            return (gray, scale);
        }


        public List<string>? CVDataMatrix(Mat image, Func<Mat, string?> analysis)
        {
            if (image == null) return null;

            List<string> res = new List<string>();
            var checkCode = analysis(image);
            if (checkCode != null)
            {
                res.Add(checkCode);
            }

            var (line, scale) = ScaleImgToGray(image);

            Cv2.Blur(line, line, new OpenCvSharp.Size(5, 5));
            ShowCV("3.滤波", line);

            //二值化 130 具体指可以根据DataMatrix码的白色的RGB值来调整
            Cv2.Threshold(line, line, 130, 255, ThresholdTypes.Binary);
            ShowCV("4.二值化", line);

            //Canny边缘检测
            Cv2.Canny(line, line, 100, 200);
            ShowCV("5.边缘检测", line);

            //消除裂缝2 Size(11, 12) Size的值可以调整，结果出来为DataMatrix码 区域全白就ok啦。
            OpenCvSharp.Size size2 = new(11, 12);
            Mat kernel = Cv2.GetStructuringElement(MorphShapes.Rect, size2);
            Cv2.MorphologyEx(line, line, MorphTypes.Close, kernel);
            // Cv2.MorphologyEx(canny_Image, canny_Image, MorphTypes.Close, canny_Image1, null,6 , BorderTypes.Constant,1);
            ShowCV("6.消除裂缝", line);

            //7.闭运算，填充条形码间隙
            Mat element = Cv2.GetStructuringElement(0, new OpenCvSharp.Size(3, 3));
            Cv2.MorphologyEx(line, line, MorphTypes.Close, element);//MORPH_CLOSE
            ShowCV("7.闭运算", line);

            //8. 腐蚀，去除孤立的点
            Cv2.Erode(line, line, element);
            ShowCV("8.腐蚀", line);

            //9. 膨胀，填充条形码间空隙，根据核的大小，有可能需要2~3次膨胀操作
            Cv2.Dilate(line, line, element);
            Cv2.Dilate(line, line, element);
            Cv2.Dilate(line, line, element);
            Cv2.Dilate(line, line, element);
            Cv2.Dilate(line, line, element);
            ShowCV("9.膨胀", line);


            OpenCvSharp.Point[][] contours;
            OpenCvSharp.HierarchyIndex[] hiera;
            //10.通过findContours找到条形码区域的矩形边界 CV_RETR_EXTERNAL CV_CHAIN_APPROX_NONE
            Cv2.FindContours(line, out contours, out hiera, RetrievalModes.External, ContourApproximationModes.ApproxNone);

            for (int i = 0; i < contours.Length; i++)
            {
                Rect rect = Cv2.BoundingRect(contours[i]);
                if (rect.Width > (line.Width / 20) && (double)Math.Abs(rect.Width - rect.Height) / (double)rect.Width < 0.2)
                {
                    rect.X = (int)(rect.X / scale);
                    rect.Y = (int)(rect.Y / scale);
                    rect.Width = (int)(rect.Width / scale);
                    rect.Height = (int)(rect.Height / scale);

                    rect = new Rect(rect.X, rect.Y, rect.Width, rect.Height);

                    int exRange = 20;
                    rect.X -= exRange;
                    if (rect.X < 0) rect.X = 0;
                    rect.Y -= exRange;
                    if (rect.Y < 0) rect.Y = 0;
                    rect.Width += exRange * 2;
                    if (rect.X + rect.Width > image.Width) rect.Width = image.Width - rect.X;
                    rect.Height += exRange * 2;
                    if (rect.Y + rect.Height > image.Height) rect.Height = image.Height - rect.Y;

                    var cut = new Mat(image, rect);
                    Cv2.Resize(cut, cut, new OpenCvSharp.Size(cut.Width * 2, cut.Height * 2));
                    checkCode = analysis(cut);
                    //Cv2.ImShow(i.ToString(), cut);
                    if (checkCode != null)
                    {
                        //Cv2.ImShow(i.ToString(), cut);
                        res.Add(checkCode);
                    }
                }
            }
            return res;

        }


        public List<string> CV128Code(Mat image, Func<Mat, List<string>?> analysis, bool roate = true)
        {
            if (image == null) return new List<string>();
            List<string> res = new List<string>();
            var checkCode = analysis(image);
            if (checkCode != null && checkCode.Count > 0)
            {
                res.AddRange(checkCode);
            }

            Mat imageGuussian = new();
            Mat imageSobelX = new(), imageSobelY = new(), imageSobelOut = new();

            var (gray, scale) = ScaleImgToGray(image);
            var source = gray;
            //3. 高斯平滑滤波
            Cv2.GaussianBlur(gray, imageGuussian, new OpenCvSharp.Size(3, 3), 0);
            ShowCV("3.高斯平衡滤波" + roate, imageGuussian);

            //4.求得水平和垂直方向灰度图像的梯度差,使用Sobel算子
            Mat imageX16S = new Mat(), imageY16S = new Mat();
            Cv2.Sobel(imageGuussian, imageX16S, MatType.CV_16S, 1, 0, 3, 1, 0, BorderTypes.Reflect101);
            Cv2.Sobel(imageGuussian, imageY16S, MatType.CV_16S, 0, 1, 3, 1, 0, BorderTypes.Reflect101);
            Cv2.ConvertScaleAbs(imageX16S, imageSobelX, 1, 0);
            Cv2.ConvertScaleAbs(imageY16S, imageSobelY, 1, 0);
            imageSobelOut = imageSobelX - imageSobelY;
            ShowCV("4.X方向梯度" + roate, imageSobelX);
            ShowCV("4.Y方向梯度" + roate, imageSobelY);
            ShowCV("4.XY方向梯度差" + roate, imageSobelOut);

            //5.均值滤波，消除高频噪声
            Cv2.Blur(imageSobelOut, imageSobelOut, new OpenCvSharp.Size(1, 2));
            ShowCV("5.均值滤波" + roate, imageSobelOut);

            //6.二值化
            Mat imageSobleOutThreshold = new Mat();
            Cv2.Threshold(imageSobelOut, imageSobleOutThreshold, 180, 255, ThresholdTypes.Binary);//CV_THRESH_BINARY
            ShowCV("6.二值化" + roate, imageSobleOutThreshold);

            //7.闭运算，填充条形码间隙
            Mat element = Cv2.GetStructuringElement(0, new OpenCvSharp.Size(5, 5));
            Cv2.MorphologyEx(imageSobleOutThreshold, imageSobleOutThreshold, MorphTypes.Close, element);//MORPH_CLOSE
            ShowCV("7.闭运算" + roate, imageSobleOutThreshold);

            //8. 腐蚀，去除孤立的点
            Cv2.Erode(imageSobleOutThreshold, imageSobleOutThreshold, element);
            ShowCV("8.腐蚀" + roate, imageSobleOutThreshold);

            //9. 膨胀，填充条形码间空隙
            Cv2.Dilate(imageSobleOutThreshold, imageSobleOutThreshold, element);
            Cv2.Dilate(imageSobleOutThreshold, imageSobleOutThreshold, element);
            Cv2.Dilate(imageSobleOutThreshold, imageSobleOutThreshold, element);
            ShowCV("9.膨胀" + roate, imageSobleOutThreshold);

            OpenCvSharp.Point[][] contours;
            OpenCvSharp.HierarchyIndex[] hiera;

            //10.通过findContours找到条形码区域的矩形边界 CV_RETR_EXTERNAL CV_CHAIN_APPROX_NONE
            Cv2.FindContours(imageSobleOutThreshold, out contours, out hiera, RetrievalModes.External, ContourApproximationModes.ApproxNone);


            var area = (double)gray.Width * gray.Height / 60;

            for (int i = 0; i < contours.Length; i++)
            {
                Rect rect = Cv2.BoundingRect(contours[i]);

                if (rect.Width * rect.Height > area)
                //if (true)
                {
                    if (source == image)
                    {
                        rect.X = (int)(rect.X / scale);
                        rect.Y = (int)(rect.Y / scale);
                        rect.Width = (int)(rect.Width / scale);
                        rect.Height = (int)(rect.Height / scale);
                        rect = new Rect(rect.X, rect.Y, rect.Width, rect.Height);
                    }

                    int exRange = 20;
                    rect.X -= exRange;
                    if (rect.X < 0) rect.X = 0;
                    rect.Y -= exRange;
                    if (rect.Y < 0) rect.Y = 0;
                    rect.Width += exRange * 2;
                    if (rect.X + rect.Width > image.Width) rect.Width = image.Width - rect.X;
                    rect.Height += exRange * 2;
                    if (rect.Y + rect.Height > image.Height) rect.Height = image.Height - rect.Y;


                    if (0 <= rect.X && 0 <= rect.Width && rect.X + rect.Width <= source.Cols && 0 <= rect.Y && 0 <= rect.Height && rect.Y + rect.Height <= source.Rows)
                    {
                        //Cv2.Rectangle(image, rect, new Scalar(255), 2);
                        var cut = new Mat(source, rect);

                        var codes = analysis(cut);

                        if (codes != null && codes.Count > 0)
                        {
                            res.AddRange(codes);
                        }
                    }
                }
            }

            //Cv2.ImShow("10.找出二维码矩形区域", image);

            if (roate)
            {
                Mat image2 = new Mat();
                Cv2.Rotate(image, image2, RotateFlags.Rotate90Counterclockwise);
                res.AddRange(CV128Code(image2, analysis, false));
            }

            return res.Distinct().ToList();
            //Cv2.WaitKey();
        }


    }
}
